#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/sched.h>
#include <net/sock.h>
#include "kmsg_driver.h"

#define BUF_SIZE 16384

static struct sock *kmsg_sock = NULL;
static struct workqueue_struct *kmsg_wq = NULL;
static struct work_struct kmsg_work;

spinlock_t msg_list_lock = SPIN_LOCK_UNLOCKED;
LIST_HEAD(msg_list);

/*########### for ksmg interface ########## */

kmsg_node_t* alloc_kmsg_node(void *msg, kmsg_end_msg_t *end_msg, void *priv_data)
{
	kmsg_node_t *node = NULL;
	ENTER();

	node = (kmsg_node_t *)kmalloc(sizeof(kmsg_node_t), GFP_KERNEL);
	if (!node)
		goto out;
	
	atomic_set(&node->ref_cnt, 1);
	INIT_LIST_HEAD(&node->list);
	node->msg = msg;
	node->end_msg = end_msg;
	node->priv_data = priv_data;
out:
	LEAVE("node:%p", p);
	return node;
}
EXPORT_SYMBOL(alloc_kmsg_node);

void kmsg_send(kmsg_node_t *node)
{
	unsigned long flag;
	ENTER("node:%p", node);

	spin_lock_irqsave(&msg_list_lock, flag);
	list_add_tail(&node->list, &msg_list);
	spin_unlock_irqrestore(&msg_list_lock, flag);

	queue_work(kmsg_wq, &kmsg_work);
	LEAVE();
}
EXPORT_SYMBOL(kmsg_send);

void kmsg_node_put(kmsg_node_t *node)
{
	int free = 0;
	ENTER();
	if (!atomic_dec_and_test(&node->ref_cnt)) {
		free = 1;
		goto out;
	}
	list_del(&node->list);
//	mem_clear(node->msg);
	kfree(node->msg);
//	mem_clear(node);
	kfree(node);
out:
	LEAVE("node:%p:free:%d", node, free);
}
EXPORT_SYMBOL(kmsg_node_put);

/* ################## internel functions ##########*/

static void destroy_kmsg_socket(void)
{
	sock_release(kmsg_sock->sk_socket);
}

static int create_kmsg_socket(void)
{
	int ret = 0;
	ENTER();

        kmsg_sock = netlink_kernel_create(NETLINK_KMSG, 1, NULL, THIS_MODULE);
        if (!kmsg_sock) {
                KMSG_ERR("Fail to create netlink socket.\n");
		ret = -ENOMEM;
        }

	LEAVE("ret:%d", ret);
	return ret;
}

static void destroy_kmsg_wq(void)
{
	ENTER();
	if (kmsg_wq) {
		destroy_workqueue(kmsg_wq);
		kmsg_wq = NULL;
	}
	LEAVE();
}

void kmsg_fill_skb(struct sk_buff *skb, kmsg_msg_t *msg)
{
	int size = NLMSG_SPACE(sizeof(*msg));
	struct nlmsghdr *nlh;

	nlh = (struct nlmsghdr *)skb_put(skb, size);
	nlh->nlmsg_len = size;
	memcpy(NLMSG_DATA(nlh), msg, sizeof(*msg));

	nlh->nlmsg_pid = 0;
	nlh->nlmsg_flags = 0;
	NETLINK_CB(skb).pid = 0;
	NETLINK_CB(skb).dst_pid = 0;
	NETLINK_CB(skb).dst_group = 1;
}

int kmsg_sendmsg_sync(kmsg_msg_t *msg)
{
	struct sk_buff *skb = NULL;
	int ret = 0;
	
	ENTER();
	skb = alloc_skb(NLMSG_SPACE(sizeof(kmsg_msg_t)), GFP_KERNEL);
	if (!skb) {
		KMSG_ERR("Alloc skb failed!");
		ret = -ENOMEM;
		goto out0;
	}
	/*export this func */
	kmsg_fill_skb(skb, msg);

	netlink_broadcast(kmsg_sock, skb, 0, 1, GFP_KERNEL);
out0:
	LEAVE("ret:%d", ret);
	return ret;
}

static void kmsg_main(void *data)
{
	kmsg_node_t *node, *tmp;
	unsigned long flag;
	struct list_head list;

	INIT_LIST_HEAD(&list);

	spin_lock_irqsave(&msg_list_lock, flag);
	list_replace_init(&msg_list, &list);
	spin_unlock_irqrestore(&msg_list_lock, flag);

	list_for_each_entry_safe(node, tmp, &list, list) {
		int ret;
		list_del(&node->list);
		ret = kmsg_sendmsg_sync(node->msg);
		node->end_msg(node, 0, ret);
		kmsg_node_put(node);	
		cond_resched(); //TODO:why?
	}
}

static int create_kmsg_wq(void)
{
	int ret = 0;
	ENTER()

	kmsg_wq = create_singlethread_workqueue("kmsg_work");
	if (!kmsg_wq) {
		KMSG_ERR("Create workqueue for kmsg failed");
		ret = -ENOMEM;
	}
	INIT_WORK(&kmsg_work, kmsg_main, NULL);

	LEAVE("ret:%d", ret);
	return ret;
}

static int __init kmsg_init(void)
{
	int ret = 0;
	ENTER();

	ret = create_kmsg_socket();
	if (ret < 0)
		goto out;

	ret = create_kmsg_wq();
	if (ret < 0)
		goto out1;
out1:
	destroy_kmsg_socket();
out:
	LEAVE("ret:%d", ret);
        return ret;
}

static void __exit kmsg_exit(void)
{
	destroy_kmsg_wq();
	destroy_kmsg_socket();
}

module_init(kmsg_init);
module_exit(kmsg_exit);

MODULE_LICENSE("GPL");
